﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Text;
using System.Threading;
using System.Xml;

namespace Workflows.Components.TextMining.Clustering
{
    /// <summary>
    /// it takes an xml file in the following format
    /// a node can have only one parent node at most
    /// a leaf can have 1 or more parent node
    /// distance is normalized and in the range of (0,1)
    /// <Diagram>
    ///     <Nodes>
    ///         <Node NodeID="0" Label="" ParentNodeID="1" Distance="0.01"></Node>
    ///     </Nodes>
    ///     <Leaves>
    ///         <Leaf LeafID="0" Label="">
    ///             <Parent NodeID="0" Distance="0.1"/>
    ///             <Parent NodeID="23" Distance="0.233"/>
    ///         </Leaf>
    ///     </Leaves>
    /// </Diagram>
    /// </summary>
    public class NetworkDiagram:IDisposable
    {
        #region delegates
        /// <summary>
        /// 
        /// </summary>
        /// <param name="img"></param>
        public delegate void DiagramUpdated(Image img);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="img"></param>
        /// <param name="nodePositions"></param>
        /// <param name="leafPositions"></param>
        public delegate void DiagramFinished(
            Image img, Dictionary<int, Point> nodePositions, Dictionary<int, Point> leafPositions);

        public event DiagramFinished OnDiagramFinished;
        public event DiagramUpdated OnDiagramUpdated;
        #endregion

        #region structures
        /// <summary>
        /// 
        /// </summary>
        public struct Link:IComparable
        {
            public int FromID;
            public int ToID;
            public bool FromVerticeIsNode;
            public double Distance;

            #region IComparable Members

            public int CompareTo(object obj)
            {
                if(obj.GetType()==typeof(Link))
                {
                    Link otherLink = (Link) obj;
                    return this.Distance.CompareTo(otherLink.Distance);
                }
                else
                {
                    return 0;
                }
            }

            #endregion
        }
        /// <summary>
        /// 
        /// </summary>
        public struct Node
        {
            public int NodeID;
            public string Label;
        }
        /// <summary>
        /// 
        /// </summary>
        public struct Leaf
        {
            public int LeafID;
            public string Label;
        }
        #endregion

        #region fields
        private Dictionary<int, Link> _NodeNodeLinks;    // child node id -> Link
        private Dictionary<int, Dictionary<int,Link>> _LeafNodeLinks;    // leaf id -> (ToNodeID -> Link)
        private Dictionary<int, Node> _Nodes;
        private Dictionary<int, Leaf> _Leaves;

        private Bitmap _Image;
        private Graphics _Graphics;
        private Size _Bounds;
        private Thread _LayoutThread;

        // layout
        private Dictionary<int, Point> _NodePositions;
        private Dictionary<int, Point> _LeafPositions;
        private const double DeltaRate = -0.25;
        private const int MaxIterationCount = 100;
        const int NodeRadius = 8;
        const int LeafLen = 6;
        #endregion

        #region init
        /// <summary>
        /// 
        /// </summary>
        /// <param name="bounds"></param>
        /// <param name="inputFilePath"></param>
        public NetworkDiagram(Size bounds, string inputFilePath)
        {
            this._Bounds = bounds;
            this.ReadInputData(inputFilePath);
            this._Image = new Bitmap(this._Bounds.Width,this._Bounds.Height);
            this._Graphics = Graphics.FromImage(this._Image);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="bounds"></param>
        /// <param name="nodes"></param>
        /// <param name="leaves"></param>
        /// <param name="nodeLinks"></param>
        /// <param name="leafLinks"></param>
        public NetworkDiagram(Size bounds, 
            Dictionary<int,Node> nodes,
            Dictionary<int,Leaf> leaves,
            Dictionary<int,Link> nodeLinks,
            Dictionary<int,Dictionary<int,Link>> leafLinks)
        {
            this._Nodes = nodes;
            this._Leaves = leaves;
            this._NodeNodeLinks = nodeLinks;
            this._LeafNodeLinks = leafLinks;

            this._Bounds = bounds;
            this._Image = new Bitmap(this._Bounds.Width, this._Bounds.Height);
            this._Graphics = Graphics.FromImage(this._Image);
        }

        private void ReadInputData(string inputXmlFilePath)
        {
            this._Nodes=new Dictionary<int, Node>();
            this._Leaves=new Dictionary<int, Leaf>();
            this._NodeNodeLinks=new Dictionary<int, Link>();
            this._LeafNodeLinks=new Dictionary<int, Dictionary<int, Link>>();

            XmlDocument xDoc = new XmlDocument();
            xDoc.Load(inputXmlFilePath);
            XmlNodeList xNodeList = xDoc.SelectNodes("//Node");
            foreach (XmlNode xNode in xNodeList)
            {
                int nodeID = int.Parse(xNode.Attributes.GetNamedItem("NodeID").Value);
                int parentNodeID = int.Parse(xNode.Attributes.GetNamedItem("ParentNodeID").Value);
                string label = xNode.Attributes.GetNamedItem("Label").Value;
                double distance = double.Parse(xNode.Attributes.GetNamedItem("Distance").Value);
                Node node =new Node();
                node.NodeID = nodeID;
                node.Label = label;
                this._Nodes.Add(nodeID,node);

                if(parentNodeID>0)
                {
                    Link link = new Link();
                    link.Distance = distance;
                    link.FromID = nodeID;
                    link.FromVerticeIsNode = true;
                    link.ToID = parentNodeID;
                    this._NodeNodeLinks.Add(nodeID,link);
                }
            }

            XmlNodeList leafNodeList = xDoc.SelectNodes("//Leaf");
            foreach(XmlNode leafNode in leafNodeList)
            {
                int leafID = int.Parse(leafNode.Attributes.GetNamedItem("LeafID").Value);
                string label = leafNode.Attributes.GetNamedItem("Label").Value;
                Leaf leaf = new Leaf();
                leaf.LeafID = leafID;
                leaf.Label = label;
                this._Leaves.Add(leafID, leaf);

                XmlNodeList leafLinkNodes = leafNode.SelectNodes("Parent");
                Dictionary<int, Link> leafLinks = new Dictionary<int, Link>();
                foreach (XmlNode linkNode in leafLinkNodes)
                {
                    Link link = new Link();
                    link.Distance = double.Parse(linkNode.Attributes.GetNamedItem("Distance").Value);
                    link.FromID = leafID;
                    link.FromVerticeIsNode = false;
                    link.ToID = int.Parse(linkNode.Attributes.GetNamedItem("NodeID").Value);
                    leafLinks.Add(link.ToID, link);
                }
                this._LeafNodeLinks.Add(leafID, leafLinks);
            }
        }

        private void DrawStartDiagram()
        {
            Color nodeFill = Color.FromArgb(125, Color.Orange);
            Color nodeBorder = Color.FromArgb(125, Color.Red);
            Color leafFill = Color.FromArgb(125, Color.Blue);
            Color leafBorder = Color.FromArgb(125, Color.Violet);

            this._NodePositions=new Dictionary<int, Point>();
            this._LeafPositions=new Dictionary<int, Point>();
            Random random = new Random((int) (DateTime.Now.Ticks%100000));
            this._Graphics.Clear(Color.White);
            this._Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            
            // populate positions 
            foreach(int nodeID in this._Nodes.Keys)
            {
                Point point = new Point(random.Next(this._Bounds.Width), random.Next(this._Bounds.Height));
                this._NodePositions.Add(nodeID,point);
            }
            foreach(int leafID in this._Leaves.Keys)
            {
                Point point =new Point(random.Next(this._Bounds.Width), random.Next(this._Bounds.Height));
                this._LeafPositions.Add(leafID,point);
            }
            // draw links first
            foreach(int nodeID in this._NodeNodeLinks.Keys)
            {
                Point fromPos = this._NodePositions[nodeID];
                Link lnk = this._NodeNodeLinks[nodeID];
                Point toPos = this._NodePositions[lnk.ToID];
                this._Graphics.DrawLine(new Pen(Color.Black,1), fromPos, toPos);
            }
            foreach(int leafID in this._LeafNodeLinks.Keys)
            {
                Point fromPos = this._LeafPositions[leafID];
                Dictionary<int, Link> links = this._LeafNodeLinks[leafID];
                foreach(int toNodeID in links.Keys)
                {
                    Point toPos = this._NodePositions[toNodeID];
                    this._Graphics.DrawLine(new Pen(Color.DarkGray,1), fromPos,toPos);
                }
            }
            // draw node 
            foreach (int nodeID in this._Nodes.Keys)
            {
                Point point = this._NodePositions[nodeID];
                Rectangle rect = new Rectangle(point.X - NodeRadius, point.Y - NodeRadius, NodeRadius * 2, NodeRadius * 2);
                this._Graphics.FillEllipse(new SolidBrush(nodeFill), rect);
                this._Graphics.DrawEllipse(new Pen(nodeBorder, 1), rect);
            }
            // draw leaf
            foreach (int leafID in this._Leaves.Keys)
            {
                Point point = this._LeafPositions[leafID];
                Rectangle rect = new Rectangle(point.X - LeafLen / 2, point.Y - LeafLen / 2, LeafLen, LeafLen);
                this._Graphics.FillRectangle(new SolidBrush(leafFill), rect);
                this._Graphics.DrawRectangle(new Pen(leafBorder, 1), rect);
            }

            // debug
            this._Image.Save(@"c:\tmp.jpg",ImageFormat.Jpeg);

            if (this.OnDiagramUpdated != null)
                this.OnDiagramUpdated(this._Image);
        }
        #endregion

        #region public method
        /// <summary>
        /// layout returns updated image asynchronously, calling UI thread needs to 
        /// know how to handle image update and avoiding flick by double-buffer enabling 
        /// the control
        /// </summary>
        public void StartLayout()
        {
            if(this._LayoutThread !=null && this._LayoutThread.IsAlive)
            {
                this._LayoutThread.Abort();
                this._LayoutThread.Join();
            }

            this._LayoutThread=new Thread(new ThreadStart(this.TryLayout));
            this._LayoutThread.Start();
        }

        private void TryLayout()
        {
            this.DrawStartDiagram();

            int iterationCount = 0;

            while(StillNeedUpdate())
            {
                this.TryFit();
                this.UpdateSimilarityMatrix();

                if (this.OnDiagramUpdated != null)
                    this.OnDiagramUpdated(this._Image);

                iterationCount += 1;
                if(iterationCount > MaxIterationCount)
                    break;
            }

            if (this.OnDiagramFinished != null)
                this.OnDiagramFinished(this._Image, this._NodePositions, this._LeafPositions);
        }
        #endregion

        #region adjust
        private bool StillNeedUpdate()
        {
            double currentMoveDistance = 10;
            double nextMoveDistance = currentMoveDistance*(1 + DeltaRate);
            if (nextMoveDistance < 0.5)
                return false;
            else
                return false;
        }

        private void UpdateSimilarityMatrix()
        {
            
        }

        private void TryFit()
        {
            Bitmap imgClone = (Bitmap)this._Image.Clone();
            this._Graphics = Graphics.FromImage(imgClone);
            this._Graphics.Clear(Color.White);
            this._Graphics.SmoothingMode = SmoothingMode.AntiAlias;


            this._Image = imgClone;
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            this._Graphics = null;
            this._Graphics.Dispose();
            this._Image = null;
            this._Image.Dispose();
        }

        #endregion
    }
}
